#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <sys/vfs.h>
#include <linux/vm_sockets.h>

#define FUN_SUCCESS (0)
#define INVALID_INPARAMS    (-1)
#define MALLOC_FAIL (-2)
#define DATA_POOL_ADD_ITEM_FAIL  (-3)
#define DATA_POOL_REMOVE_ITEM_FAIL  (-4)
#define DATA_POOL_GET_ITEM_FAIL  (-5)
#define DATA_SEND_FAIL  (-6)
#define DATA_RECV_FAIL  (-7)
#define CONNECT_FAIL  (-8)
#define FUNCTION_CALL_FAIL (-9)
#define CONNECT_CLOSE   (-10)
#define BUF_LEN_NOT_ENOUGH      (-11)

typedef enum {
	WAIT,
	SEND,
	FUNIN,
	FUNOUT,
	RECEIVE,
	UNUSUAL,
} status_st;

typedef enum {
        DATA_ID_BEGIN = 0,
        DATA_ID_END = 7,
        DATA_ID_LEN = 8,
        DATA_LEN_BEGIN = 8,
        DATA_LEN_END = 8,
        DATA_LEN_LEN = 1,
        FUN_RET_BEGIN = 9,
        FUN_RET_END = 12,
        FUN_RET_LEN = 4,
        PREV_DATA_LEN = 13,
} data_layout;

typedef struct data_pool {
	uint64_t data_id;
	uint8_t *data;
	size_t data_len;
	struct data_pool *prev;
	struct data_pool *next;
	pthread_cond_t *cond;
	pthread_mutex_t *lock;
	status_st status;
	int ret;
} data_pool_t;

static uint64_t counter = 0;
pthread_mutex_t counter_lock = PTHREAD_MUTEX_INITIALIZER;
static data_pool_t *header = NULL;

static uint64_t get_counter(void)
{
	uint64_t counter_v;
	(void)pthread_mutex_lock(&counter_lock);
	counter++;
	counter_v = counter;
	(void)pthread_mutex_unlock(&counter_lock);
	return counter_v;
}

static data_pool_t *new_data_item(void)
{
	data_pool_t *item = NULL;
	int ret;

	item = (data_pool_t *)malloc(sizeof(data_pool_t));
	if (item == NULL) {
		return NULL;
	}

	item->cond = (pthread_cond_t *)malloc(sizeof(pthread_cond_t));
	if (item->cond == NULL) {
		goto free_item;
	}

	item->lock = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
	if (item->lock == NULL) {
		goto free_mutex;
	}

	ret = pthread_cond_init(item->cond, NULL);
	if (ret != 0) {
		goto free_mutex;
	}

	ret = pthread_mutex_init(item->lock, NULL);
	if (ret != 0) {
		(void)pthread_cond_destroy(item->cond);
		goto free_mutex;
	}

	return item;

free_mutex:
	free(item->lock);
	item->lock = NULL;
free_cond:
	free(item->cond);
	item->cond = NULL;
free_item:
	free(item);
	item = NULL;

	return item;
}

static void free_data_item(data_pool_t *item)
{
	if (item != NULL && item->cond != NULL) {
		pthread_cond_destroy(item->cond);
		free(item->cond);
		item->cond = NULL;
	}

	if (item != NULL && item->lock != NULL) {
		pthread_mutex_destroy(item->lock);
		free(item->lock);
		item->lock = NULL;
	}

	if (item != NULL) {
		free(item);
		item = NULL;
	}
}

int pool_header_init(void)
{
	header = new_data_item();
	if (header == NULL) {
		return MALLOC_FAIL;
	}

	header->data = NULL;
	header->data_len = 0;
	header->next = NULL;
	header->prev = NULL;
	header->data_id = 0;
	header->status = UNUSUAL;

	return FUN_SUCCESS;
}

void pool_header_fini(void)
{
	free_data_item(header);
}

static int add_item(data_pool_t *pool_header, void *data, size_t data_len, uint64_t data_id, status_st status)
{
	data_pool_t *new_item = NULL;
	data_pool_t *item = NULL;
	
	if (pool_header == NULL || data == NULL || data_len == 0) {
		return INVALID_INPARAMS;
	}

	new_item = new_data_item();
	if (new_item == NULL) {
		return MALLOC_FAIL;
	}

	new_item->data = data;
	new_item->data_len = data_len;
	new_item->data_id = data_id;
	new_item->status = status;

	(void)pthread_mutex_lock(pool_header->lock);
	item = pool_header;
	while (item->next != NULL) {
		item = item->next;
	}

	item->next = new_item;
	new_item->prev = item;
	new_item->next = NULL;
	(void)pthread_mutex_unlock(pool_header->lock);

	return FUN_SUCCESS;
}

static void del_item(data_pool_t *pool_header, uint64_t data_id)
{
	data_pool_t *item = NULL;

	(void)pthread_mutex_lock(pool_header->lock);
	item = pool_header;
	while (item->data_id != data_id) {
		item = item->next;
	}

	item->prev->next = item->next;
	if (item->next != NULL) {
		item->next->prev = item->prev;
	}

	free_data_item(item);
	(void)pthread_mutex_unlock(pool_header->lock);
}

static data_pool_t *get_item_id(data_pool_t *pool_header, uint64_t data_id)
{
	data_pool_t *item = NULL;

	if (pool_header == NULL) {
		return NULL;
	}

	(void)pthread_mutex_lock(pool_header->lock);
	item = pool_header;
	while (item != NULL) {
		if (item->data_id == data_id) {
			break;
		}
		item = item->next;
	}
	(void)pthread_mutex_unlock(pool_header->lock);

	return item;
}

static data_pool_t *get_item_status(data_pool_t *pool_header, status_st status)
{
	data_pool_t *item = NULL;

	if (pool_header == NULL) {
		return NULL;
	}

	(void)pthread_mutex_lock(pool_header->lock);
	item = pool_header;
	while (item != NULL) {
		if (item->status == status) {
			break;
		}
		item = item->next;
	}
	(void)pthread_mutex_unlock(pool_header->lock);

	return item;
}

static void printf_data(uint8_t *data, size_t data_len)
{
	size_t i;
	printf("data_len: %d\n", (int)data_len);
	for (i = 0; i < data_len; i++) {
		printf("%0x ", data[i]);
	}
	printf("\n");
}

static int socket_fd = 0;
static int server_fd = 0;

static int handle_server_send(void)
{
	data_pool_t *send_item = NULL;
	char prev_data[PREV_DATA_LEN] = {0};
	int ret;

	send_item = get_item_status(header, FUNOUT);
	if (send_item != NULL) {
		memcpy(prev_data, &(send_item->data_id), DATA_ID_LEN);
		memcpy(prev_data + DATA_ID_LEN, &(send_item->data_len), DATA_LEN_LEN);
		memcpy(prev_data + DATA_ID_LEN + DATA_LEN_LEN, &(send_item->ret), FUN_RET_LEN);

		printf("server send first\n");
		printf_data(prev_data, PREV_DATA_LEN);
		ret = send(socket_fd, prev_data, PREV_DATA_LEN, 0);
		printf("server send first end\n");
		if (ret < 0) {
			printf("server send error\n");
			return DATA_SEND_FAIL;
		}

		if (send_item->data_len != 0) {
			printf("server send second\n");
			printf_data(send_item->data, send_item->data_len);
			ret = send(socket_fd, send_item->data, send_item->data_len, 0);
			printf("server send second end\n");
			if (ret < 0) {
				printf("server send second error\n");
				return DATA_SEND_FAIL;
			}
		}

		del_item(header, send_item->data_id);
	}

	return FUN_SUCCESS;
}

static bool server_send_break = false;
static int server_send(void)
{
	int ret;

	printf("server_send\n");
	while(1) {
		ret = handle_server_send();
		if (ret != FUN_SUCCESS) {
			break;
		}
	}

	return ret;
}

static int handle_server_recv(void)
{
	data_pool_t *item;
	uint64_t data_id;
	size_t data_len;
	uint8_t *data = NULL;
	char prev_data[PREV_DATA_LEN] = {0};
	int ret;
	printf("server recv first\n");
	ret = recv(socket_fd, prev_data, PREV_DATA_LEN, 0);
	printf_data(prev_data, PREV_DATA_LEN);
	printf("server recv first end\n");
	if (ret == 0) {
		return CONNECT_CLOSE;
	}
	
	if (ret < 0) {
		printf("server recv first failed\n");
		return DATA_RECV_FAIL;
	}

	memcpy(&data_id, prev_data, DATA_ID_LEN);
	data_len = prev_data[DATA_ID_LEN];
	if (data_len != 0) {
		data = (uint8_t *)malloc(data_len);
		if (data == NULL) {
			return MALLOC_FAIL;
		}

		printf("server recv second\n");
		ret = recv(socket_fd, data, data_len, 0);
		printf_data(data, data_len);
		printf("server recv second end\n");
		if (ret < 0) {
			printf("server recv second error\n");
			free(data);
			data = NULL;
			return DATA_RECV_FAIL;
		}
	}

	ret = add_item(header, data, data_len, data_id, FUNIN);
	if (ret != FUN_SUCCESS) {
		if (data != NULL) {
			free(data);
			data = NULL;
		}
		return DATA_POOL_ADD_ITEM_FAIL;
	}

	return FUN_SUCCESS;
}

static bool server_recv_break = false;
static int server_recv(void)
{
    int ret;

    printf("server_recv\n");
    while(1) {
        ret = handle_server_recv();
        if (server_recv_break || ret != FUN_SUCCESS) {
            break;
        }
    }

    return ret;
}

#define VMADDR_CID_ANY ((uint32_t)-1)
static int server_run(char *ip, int port)
{
	int fd;
	int connect_fd;
	pthread_t send_id;
	pthread_t recv_id;
	struct sockaddr_vm server_addr;
	int ret;

	fd = socket(AF_VSOCK, SOCK_STREAM, 0);
	if (fd < 0) {
		return CONNECT_FAIL;
	}

	memset(&server_addr, 0, sizeof(server_addr));
	// server_addr.sin_family = AF_INET;
	// server_addr.sin_port = htons(port);
	// server_addr.sin_addr.s_addr = inet_addr(ip);

    server_addr.svm_family = AF_VSOCK;
    server_addr.svm_cid = VMADDR_CID_ANY;
    server_addr.svm_port = port;

	ret = bind(fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
	if (ret < 0) {
		close(fd);
		return CONNECT_FAIL;
	}

	ret = listen(fd, SOMAXCONN);
	if (ret < 0) {
		close(fd);
		return CONNECT_FAIL;
	}

	connect_fd = accept(fd, (struct sockaddr*)NULL, NULL);
	if (connect_fd < 0) {
		close(fd);
		return CONNECT_FAIL;
	}
	socket_fd = connect_fd;
	server_fd = fd;

	if (pthread_create(&send_id, NULL, (void *)server_recv, NULL) != 0) {
		printf("failed to create pthread for recv data\n");
		exit(1);
	}

	if (pthread_create(&recv_id, NULL, (void *)server_send, NULL) != 0) {
		printf("failed to create pthread for send data\n");
		exit(1);
	}

	return FUN_SUCCESS;
}

int server_init(char *ip, int port)
{
	int ret;

	ret = pool_header_init();
	if (ret != FUN_SUCCESS) {
		return ret;
	}

	ret = server_run(ip, port);
	if (ret != FUN_SUCCESS) {
		pool_header_fini();
	}

	return ret;
}

typedef int (*handle_call_ft)(void *in_buf, size_t in_buf_len, void **out_buf, size_t *out_buf_len);
typedef void (*clean_buf_ft)(void *out_buf);

static bool server_handle_break = false;
int server_handle(handle_call_ft handle_call_fun, clean_buf_ft clean_buf_fun)
{
	void *in_buf = NULL;
	size_t in_buf_len = 0;
	void *out_buf = NULL;
	size_t out_buf_len = 0;
	data_pool_t *item = NULL;
	int ret;

	while(1) {
        if (server_handle_break) {
            break;
        }
		out_buf_len = 0;
		item = get_item_status(header, FUNIN);
		if (item == NULL) {
			continue;
		}
		in_buf = item->data;
		in_buf_len = item->data_len;
		ret = handle_call_fun(in_buf, in_buf_len, &out_buf, &out_buf_len);
		item->ret = ret;
		if (item->data != NULL) {
			free(item->data);
			item->data = NULL;
		}

		if (out_buf_len != 0) {
			item->data = malloc(out_buf_len);
			if (item->data == NULL) {
				return MALLOC_FAIL;
			}
			memcpy(item->data, out_buf, out_buf_len);
		}
		item->data_len = out_buf_len;
		item->status = FUNOUT;
		clean_buf_fun(out_buf);
	}

	return FUN_SUCCESS;
}

static void clear_data_pool(void)
{
	data_pool_t *item = NULL;
	data_pool_t *parent_item = NULL;

	item = header;
	while (item->next != NULL) {
		item = item->next;
	}

	while (item != header) {
		parent_item = item->prev;
		if (item->data != NULL) {
			free(item->data);
			item->data = NULL;
		}
		item->data_len = 0;
		free_data_item(item);
		item = parent_item;
	}
}

void server_fini(void)
{
    server_send_break = true;
    server_recv_break = true;
    server_handle_break = true;
    close(socket_fd);
    close(server_fd);
    clear_data_pool();
    pool_header_fini();
}
